/*
 * Copyright 2009 Eat Local Food, LLC
 *
 * This file is part of gwtCommerce.
 *
 * gwtCommerce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * gwtCommerce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with gwtCommerce.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.elf.gwt.common.client.components;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import com.elf.gwt.common.client.CommonComponentController;
import com.elf.gwt.common.client.access.JSONServerAccess;
import com.elf.gwt.common.client.access.JSONServerAccessCallback;
import com.elf.gwt.common.client.access.RequestParameter;
import com.elf.gwt.common.client.callback.OnBuiltCallback;
import com.elf.gwt.common.client.exception.JSONServerAccessNotEnabledException;
import com.elf.gwt.common.client.exception.NoResultsFoundException;
import com.elf.gwt.common.client.utils.CommonUtils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.InlineLabel;


/**
 * Simple Display Component
 */
public abstract class CommonSimpleComponent extends FlowPanel
{
	public static final String LOGIN_COMPONENT = "AccountComponent";
	public static final String LOGIN_PREFIX = LOGIN_COMPONENT + "--LoginAccount";
	
	protected boolean enableJSONServerAccess = false;
	protected boolean loginRequired = false;

	protected Throwable accessException = null;
	protected JSONServerAccess jsonServerAccess;
	protected HashMap<String, RequestParameter> requestParameters;

	protected boolean withWait = false;
	public static boolean inuse = false;
	protected Timer timer;
	protected int duration = 0;

	protected CommonComponentController controller;
	protected List<OnBuiltCallback> callbacks;

	protected String stylePrefix;
	protected String handle;
	protected CommonSimpleComponent thisComponent;

	protected FocusWidget focusWidget = null;

	/**
	 * Class that defines the callback behavior on the JSONServerAccess Response.
	 * 
	 */
	protected class ComponentJSONServerAccessCallback implements JSONServerAccessCallback 
	{
		public void onError(List<RequestParameter> requestParameterList, Throwable exception)
		{
			if (inuse) {
				accessException = new NoResultsFoundException(exception);
				build(accessException);
			}
		}

		public void onResponseReceived(List<RequestParameter> requestParameterList, JSONValue jsonValue)
		{
			if (inuse) {
				try {
					buildLocal(jsonValue);
				}
				catch (Exception e) {
					accessException = e;
					build(e);
				}
			}
		}
	}

	/**
	 * Constructor
	 */

	public CommonSimpleComponent(CommonComponentController controller)
	{
		this(controller, null, false);
	}	

	/**
	 * Constructor
	 */

	public CommonSimpleComponent(CommonComponentController controller, boolean enableJSONServerAccess)
	{
		this(controller, null, enableJSONServerAccess);
	}	

	/**
	 * Constructor
	 */

	public CommonSimpleComponent(CommonComponentController controller, String stylePrefix)
	{
		this(controller, stylePrefix, false);
	}	

	/**
	 * Constructor
	 */
	public CommonSimpleComponent(CommonComponentController controller, String stylePrefix, boolean enableJSONServerAccess)
	{
		this.controller = controller;
		if (stylePrefix == null) stylePrefix = "";
		this.stylePrefix = stylePrefix;

		String handle = this.getClass().getName();
		int index = handle.lastIndexOf(".");
		if (index > -1) handle = handle.substring(index+1);
		this.handle = handle;

		setStyleName(stylePrefix + "Component");
		addStyleName(handle);
		thisComponent = this;
		setEnableJSONServerAccess(enableJSONServerAccess);
	}

	public void build()
	{
		build(false);
	}

	/**
	 * Build by accessing the JSON Server for information.  This is the typical
	 * method used to create display.
	 */
	public void build(boolean withWait)
	{
		if (enableJSONServerAccess) {

			this.withWait = withWait;

			if (withWait && inuse) return;
			else
			{
				if (withWait)
				{	
					CommonComponentController.enableWait();
					inuse = true;
					timer.schedule(duration); //run in duration seconds
				}
				buildRequestParameters();
				send(getRequestParameters());
				clearRequestParameters();
			}
		}
		else {
			onBuilt();
		}
	}

	public void build(Throwable exception)
	{
		GWT.log(handle, exception);
		buildFromException(exception);
		onError(exception);
	}



	/**
	 * Build from jsonValue
	 * @param jsonValue
	 */
	public void buildLocal(JSONValue jsonValue)
	throws Exception
	{
		if (enableJSONServerAccess) {
			try {
				if (!LOGIN_COMPONENT.equals(getHandle()) && 
						jsonValue != null && 
						CommonUtils.getComponentValue(jsonValue, LOGIN_COMPONENT) != null &&
						CommonUtils.getComponentValue(jsonValue, getHandle()) == null) {

					controller.hideComponents();
					timer.cancel();
					inuse = false;
					CommonComponentController.disableWait();
					Window.scrollTo(0, 0);

					controller.getComponent(LOGIN_COMPONENT).build(jsonValue);
				}
				else {
					build(jsonValue);
				}
			}
			catch (Exception e)
			{
				build(e);
			}
		}
		else {
			build(jsonValue);
		}
	}
	
	/**
	 * Build from jsonValue
	 * @param jsonValue
	 */
	public void build(JSONValue jsonValue)
	throws Exception
	{
		if (enableJSONServerAccess) {
			try {
				JSONValue componentMetaDataValue = CommonUtils.getComponentValue(jsonValue, getHandle() + "MetaData");
				if (componentMetaDataValue != null)
				{
					JSONObject jsonObject;
					if ((jsonObject = componentMetaDataValue.isObject()) != null)
					{
						buildMetaModel(jsonObject);
					}
				}

				JSONValue componentValue = CommonUtils.getComponentValue(jsonValue, getHandle());
				if (componentValue != null)
				{
					//GWT.log(componentValue.toString(), null);
					JSONArray jsonArray;
					if ((jsonArray = componentValue.isArray()) != null)
					{
						buildModel(jsonArray);
						buildFromModel();
					}
					else
					{
						accessException = new NoResultsFoundException();
						build(accessException);				
					}
				}
				else
				{
					accessException = new NoResultsFoundException();
					build(accessException);
				}
			}
			catch (Exception e)
			{
				build(e);
			}

			if (withWait)
			{	
				withWait = false;
				CommonComponentController.disableWait();
				inuse = false;
				timer.cancel();
			}
		}
		else throw new JSONServerAccessNotEnabledException();
	}

	public void rebuild()
	{
		if (enableJSONServerAccess) {
			try {
				buildFromModel();
			}
			catch (Exception e) {onError(e);}
		}
		else {
			build();
		}
	}

	protected void buildFromException(Throwable exception)
	{
		clear();
		add(new InlineLabel(exception.toString()));
	}

	public void onBuilt()
	{
		GWT.log("CommonSimpleComponent.onBuilt");
		if (callbacks == null || callbacks.size() == 0) {
			setVisible(true);
			if (focusWidget != null) {
				focusWidget.setFocus(true);
			}
		}
		else {
			GWT.log("CommonSimpleComponent.onBuilt run callbacks");
			for (Iterator<OnBuiltCallback> iter = callbacks.iterator(); iter.hasNext(); )
			{
				OnBuiltCallback callback = iter.next();
				callback.onBuilt(thisComponent);
			}
		}
	}

	public void onSelection(String selection)
	{
		History.newItem(selection);
	}

	public void onError(Throwable exception)
	{
		setVisible(false);

		if (withWait)
		{	
			withWait = false;
			CommonComponentController.disableWait();
			inuse = false;
			timer.cancel();
		}
	}

	public boolean updateFromHistoryParameters(String[] params)
	{
		GWT.log("CommonSimpleComponent.updateFromHistoryParameters " + getHandle() + " " + this.isLoginRequired() + ", " + this.controller.isLoggedIn());
		if(this.isLoginRequired() && !this.controller.isLoggedIn()) {
			onSelection(LOGIN_PREFIX + "--" + convertForRedirectToLogin(params));
			return false;
		}
		return true;
	}

	public String convertForRedirectToLogin(String[] params)
	{
		String conversion = params[0];
		for (int i = 1; i < params.length; i++)
		{
			conversion += "__" + params[i];
		}
		return conversion;
	}

	public void buildFromModel() throws Exception
	{
		focusWidget = null;
		onBuilt();
	}

	public String getTitle()
	{
		return "";
	}

	public String getHandle()
	{
		return handle;
	}

	public String getImageCommonDirectory()
	{
		return CommonUtils.getImageCommonDirectory() + getLanguageCode() + "/";
	}

	public String getImageDirectory()
	{
		return CommonUtils.getImageDirectory() + handle + "/" + getLanguageCode() + "/";
	}

	public List<OnBuiltCallback> getCallbacks() {
		return callbacks;
	}

	public void setCallbacks(
			List<OnBuiltCallback> callbacks) {
		this.callbacks = callbacks;
	}

	protected String getLanguageCode()
	{
		return "en";
	}

	public FocusWidget getFocusWidget() {
		return focusWidget;
	}

	public void setFocusWidget(FocusWidget focusWidget) {
		this.focusWidget = focusWidget;
	}

	public boolean isEnableJSONServerAccess() {
		return enableJSONServerAccess;
	}

	public void setEnableJSONServerAccess(boolean enableJSONServerAccess) {
		this.enableJSONServerAccess = enableJSONServerAccess;
		if (enableJSONServerAccess) {
			requestParameters = new HashMap<String, RequestParameter>();
			jsonServerAccess = new JSONServerAccess();
			jsonServerAccess.setCallback(new ComponentJSONServerAccessCallback());
		}
	}

	public void setupTimer()
	{
		setupTimer(20000);
	}

	public void setupTimer(int duration)
	{
		if (enableJSONServerAccess) {
			this.duration = duration;
			timer = new Timer() {
				public void run() {
					CommonComponentController.disableWait();
					inuse = false;
				}
			};
		}
	}

	/**
	 * BuildData from JSONArray
	 * @param jsonArray
	 */
	protected void buildModel(JSONArray jsonArray) throws Exception
	{
		int sz = jsonArray.size();		
		for (int i = 0; i < sz; i++)
		{
			JSONValue jsonValue = jsonArray.get(i);
			JSONObject jsonObject;
			if ((jsonObject = jsonValue.isObject()) != null)
			{
				buildModel(jsonObject);
			}
		}
	}

	/**
	 * BuildData from JSONObject
	 * @param jsonObject
	 */
	protected void buildModel(JSONObject jsonObject) throws Exception
	{
		GWT.log(jsonObject.toString(), null);
	}

	/**
	 * Build meta data
	 * @param jsonArray
	 */
	protected void buildMetaModel(JSONArray jsonArray) throws Exception
	{
		JSONValue jsonValue = jsonArray.get(0);
		JSONObject jsonObject;
		if ((jsonObject = jsonValue.isObject()) != null)
		{
			buildMetaModel(jsonObject);
		}
	}

	/**
	 * Build meta data
	 * @param jsonObject
	 */
	protected void buildMetaModel(JSONObject jsonObject) throws Exception
	{
		GWT.log(jsonObject.toString(), null);
	}

	/**
	 * Build by accessing the JSON Server
	 * @param requestParameterList
	 */
	protected void send(List<RequestParameter> requestParameterList)
	{
		jsonServerAccess.doServerAccess(requestParameterList);
	}

	public void buildRequestParameters()
	{
		clearRequestParameters();
		addRequestParameter(new RequestParameter("handle", getHandle()));
		controller.additionalRequestParameters(this);
		additionalRequestParameters();
	}

	public void additionalRequestParameters()
	{
	}

	/**
	 * Get the list of request parameters being sent to JSON Server
	 * @return requestParameters
	 */
	public List<RequestParameter> getRequestParameters() {
		List<RequestParameter> lst = new ArrayList<RequestParameter>();
		Iterator<RequestParameter> iter = requestParameters.values().iterator();
		while(iter.hasNext())
		{
			lst.add(iter.next());
		}
		return lst;
	}

	/**
	 * Clear the list of request parameters being sent to JSON Server
	 */
	public void clearRequestParameters() {
		requestParameters.clear();
	}

	/**
	 * Add to the list of request parameters being sent to JSON Server
	 */
	public void addRequestParameter(RequestParameter requestParameter) {
		//GWT.log("rp " + requestParameter.getKey(), null);
		this.requestParameters.put(requestParameter.getKey(),requestParameter);
	}

	public Throwable getAccessException() {
		return accessException;
	}

	public void setAccessException(Throwable accessException) {
		this.accessException = accessException;
	}

	public boolean isLoginRequired() {
		return loginRequired;
	}

	public void setLoginRequired(boolean loginRequired) {
		this.loginRequired = loginRequired;
	}
}
